/*
 * RemoteTableModelAdapter.java
 * 
 * Written by Joseph Bowbeer and released to the public domain,
 * as explained at http://creativecommons.org/licenses/publicdomain
 */

package jozart.remotetable;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import javax.swing.event.EventListenerList;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.TableModel;

/**
 * Adapts a TableModel to the RemoteTableModel interface. A
 * {@link ReadWriteLock} controls access to the adapted table model.
 * 
 * @author Joseph Bowbeer
 * @version 2.0
 */
public class RemoteTableModelAdapter extends UnicastRemoteObject
        implements RemoteTableModel, TableModelListener {

    /** Adaptee. */
    private final TableModel model;

    /** RemoteTableModelListener(s). */
    private final EventListenerList listenerList;

    /** Read-write lock. */
    private final ReadWriteLock rwl;

    /**
     * Adapts <tt>model</tt>.
     *
     * @throws RemoteException
     */
    public RemoteTableModelAdapter(TableModel model) throws RemoteException {
        this.model = model;
        listenerList = new EventListenerList();
        rwl = new ReentrantReadWriteLock();
    }

    /** {@inheritDoc} */
    public int getRowCount() {
        readLock().lock();
        try {
            return model.getRowCount();
        } finally {
            readLock().unlock();
        }
    }

    /** {@inheritDoc} */
    public int getColumnCount() {
        readLock().lock();
        try {
            return model.getColumnCount();
        } finally {
            readLock().unlock();
        }
    }

    /** {@inheritDoc} */
    public String getColumnName(int columnIndex) {
        readLock().lock();
        try {
            return model.getColumnName(columnIndex);
        } finally {
            readLock().unlock();
        }
    }

    /** {@inheritDoc} */
    public Class<?> getColumnClass(int columnIndex) {
        readLock().lock();
        try {
            return model.getColumnClass(columnIndex);
        } finally {
            readLock().unlock();
        }
    }

    /** {@inheritDoc} */
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        readLock().lock();
        try {
            return model.isCellEditable(rowIndex, columnIndex);
        } finally {
            readLock().unlock();
        }
    }

    /** {@inheritDoc} */
    public Object getValueAt(int rowIndex, int columnIndex) {
        readLock().lock();
        try {
            return model.getValueAt(rowIndex, columnIndex);
        } finally {
            readLock().unlock();
        }
    }

    /** {@inheritDoc} */
    public Object getValues() {
        readLock().lock();
        try {
            /* RemoteTableModelEvent fetches the values for us. */
            TableModelEvent event = new TableModelEvent(model);
            return new RemoteTableModelEvent(this, event).getData();
        } finally {
            readLock().unlock();
        }
    }

    /** {@inheritDoc} */
    public Object getValues(int firstRow, int lastRow, int column) {
        readLock().lock();
        try {
            /* RemoteTableModelEvent fetches the values for us. */
            TableModelEvent event =
                new TableModelEvent(model, firstRow, lastRow, column);
            return new RemoteTableModelEvent(this, event).getData();
        } finally {
            readLock().unlock();
        }
    }

    /** {@inheritDoc} */
    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
        writeLock().lock();
        try {
            model.setValueAt(aValue, rowIndex, columnIndex);
        } finally {
            writeLock().unlock();
        }
    }

    /** {@inheritDoc} */
    public void setValues(Object data, int firstRow, int lastRow, int column) {
        writeLock().lock();
        try {
            /* RemoteTableModelEvent sets the values for us. */
            RemoteTableModelEvent.setValues(
                model, data, firstRow, lastRow, column);
        } finally {
            writeLock().unlock();
        }
    }

    /** {@inheritDoc} */
    public void addRemoteTableModelListener(RemoteTableModelListener l)
            throws RemoteException {
        readLock().lock();
        try {

            /*
             * Send the listener a full update and, if successful,
             * add the listener to the listener list.
             */

            l.remoteTableChanged(new RemoteTableModelEvent(this,
                    new TableModelEvent(model)));

            synchronized (listenerList) {
                if (listenerList.getListenerCount() == 0) {
                    /* Start listening to adapted model. */
                    model.addTableModelListener(this);
                }
                listenerList.add(RemoteTableModelListener.class, l);
            }
        } finally {
            readLock().unlock();
        }
    }

    /** {@inheritDoc} */
    public void removeRemoteTableModelListener(RemoteTableModelListener l) {
        synchronized (listenerList) {
            listenerList.remove(RemoteTableModelListener.class, l);
            if (listenerList.getListenerCount() == 0) {
                /* Stop listening to adapted model. */
                model.removeTableModelListener(this);
            }
        }
    }

//
//  Local methods
//

    /**
     * TableModelListener implementation. Listens for changes in the 
     * adapted model. 
     * <p>
     * This method is public as an implementation side effect.
     * Do not call or override.
     */
    public void tableChanged(TableModelEvent event) {
        fireRemoteTableChanged(new RemoteTableModelEvent(this, event));
    }

    /**
     * Forwards the given notification event to all RemoteTableModelListeners
     * that registered themselves as listeners for this remote table model.
     */
    protected void fireRemoteTableChanged(RemoteTableModelEvent e) {
        // Guaranteed to return a non-null array
        Object[] listeners = listenerList.getListenerList();
        // Process the listeners last to first, notifying
        // those that are interested in this event
        for (int i = listeners.length-2; i>=0; i-=2) {
            if (listeners[i]==RemoteTableModelListener.class) {
                RemoteTableModelListener lis =
                    (RemoteTableModelListener) listeners[i+1];
                try {
                    lis.remoteTableChanged(e);
                } catch (RemoteException ex) {
                    // Warning: Unresponsive listeners will be removed!
                    System.err.println("Callback failed: " + ex);
                    System.err.println("Removing " + lis);
                    removeRemoteTableModelListener(lis);
                }
            }
        }
    }

    /**
     * Returns the read lock. The read lock should be acquired before
     * attempting to read from the adapted model directly (outside
     * this adapter). 
     */
    public Lock readLock() {
        return rwl.readLock();
    }

    /**
     * Returns the write lock. The write lock should be acquired
     * before attempting to write to the adapted model directly
     * (outside this adapter). 
     */
    public Lock writeLock() {
        return rwl.writeLock();
    }
}
